/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.maven.nodetype.writer;

import offset.nodes.maven.nodetype.vo.PropertyDefVO;
import offset.nodes.maven.nodetype.vo.ChildDefVO;
import offset.nodes.maven.nodetype.vo.NodeTypeVO;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import offset.nodes.maven.nodetype.JavaPrinter;
import offset.nodes.maven.nodetype.Name;
import offset.nodes.maven.nodetype.NodeTypeMojo;

/**
 *
 * @author Walter L�tgenau
 */
public class DAOWriter {

    NodeTypeVO nodeType;
    JavaPrinter printer;
    HashMap<String, NodeTypeVO> types;
    public static final String DAO_SUFFIX = "DAO";
    protected static final HashMap<String, String> typeToValueGetter;
    protected static final HashMap<String, String> typeToNullValue;
    protected HashSet<String> nodeTypesDone = new HashSet<String>();

    static {
        typeToValueGetter = new HashMap<String, String>();

        typeToValueGetter.put("String", "String");
        typeToValueGetter.put("boolean", "Boolean");
        typeToValueGetter.put("Calendar", "Date");
        typeToValueGetter.put("double", "Double");
        typeToValueGetter.put("long", "Long");

        typeToNullValue = new HashMap<String, String>();

        typeToNullValue.put("String", "null");
        typeToNullValue.put("boolean", "false");
        typeToNullValue.put("Calendar", "null");
        typeToNullValue.put("double", "-1.0");
        typeToNullValue.put("long", "-1L");
    }

    public DAOWriter(NodeTypeVO nodeType, JavaPrinter printer, HashMap<String, NodeTypeVO> types) {
        this.nodeType = nodeType;
        this.printer = printer;
        this.types = types;
    }

    public void write() throws IOException {
        // add generation comment
        printer.printLine(InterfaceWriter.HEADER);
        printer.appendLine();

        // add package from namespace
        Name name = new Name(nodeType.getName());

        String packageDeclaration = "package " + NodeTypeMojo.GENERATED_PACKAGE + "." + name.getPrefix() + ";";
        printer.printLine(packageDeclaration);
        printer.appendLine();

        // generate constant imports, e.g. the child node types.
        printer.printLines(InterfaceWriter.CONSTANT_IMPORTS);

        // add imports for interfaces of child node types
        Iterator<ChildDefVO> ci = nodeType.getChildren().iterator();
        while (ci.hasNext()) {
            ChildDefVO child = ci.next();

            Name childName = new Name(child.getPrimaryType());
            printer.printLine("import " + name.nameToJavaPackageAndClassName() + ";");
        }

        // add imports for interfaces of super types.
        StringBuilder interfaces = new StringBuilder();
        for (String superType : nodeType.getSuperTypes()) {
            Name superTypeName = new Name(superType);
            String interfaceImport = "import " + superTypeName.nameToJavaPackageAndClassName() + ";";
            printer.printLine(interfaceImport);
            interfaces.append(superTypeName.nameToJavaPackageAndClassName()).append(", ");
        }
        interfaces.append(name.nameToJavaPackageAndClassName());

        // add header
        printer.appendLine();
        printer.printLine("public class " + name.nameToJavaClassName() + DAO_SUFFIX + " " + "implements " + interfaces + " {");

        // generate constant methods: constructor etc.
        printer.incrementIndent();
        printer.printLine("Node node;");
        printer.appendLine();
        printer.printLine("public " + name.nameToJavaClassName() + DAO_SUFFIX + "(Node node) {"); // constructor
        printer.incrementIndent();
        printer.printLine("this.node = node;");
        printer.decrementIndent();
        printer.printLine("}");
        printer.appendLine();
        printer.printLine("@Override");
        printer.printLine("public Node getNode() {");
        printer.incrementIndent();
        printer.printLine("return node;");
        printer.decrementIndent();
        printer.printLine("}");
        printer.appendLine();

        writeMethods(nodeType);

        // add trailer
        printer.decrementIndent();
        printer.printLine("}");

        printer.close();
    }

    public void writeMethods(NodeTypeVO type) throws IOException {
        if (nodeTypesDone.contains(type.getName()))
            return;

        String interfaceName = null;
        if (type != nodeType) {
            Name typeName = new Name(type.getName());
            interfaceName = NodeTypeMojo.GENERATED_PACKAGE + Name.PACKAGE_DELIMITER + typeName.getPrefix() + Name.PACKAGE_DELIMITER + typeName.nameToJavaClassName();
        }

        // write properties
        for (PropertyDefVO property : type.getProperties()) {
            writeProperty(property, interfaceName);
        }

        // write children
        for (ChildDefVO child : type.getChildren()) {
            writeChild(child, interfaceName);
        }

        nodeTypesDone.add(type.getName());

        for (String superType : type.getSuperTypes()) {
            writeMethods(types.get(superType));
        }
    }

    public void writeProperty(PropertyDefVO property, String interfaceName) throws IOException {
        if (property.getName() == null)
            return;

        Name name = new Name(property.getName());
        String constantName = null;
        if (interfaceName != null)
            constantName = interfaceName + "." + name.nameToConstant();
        else
            constantName = name.nameToConstant();

        // generate has... method
        printer.printLine("@Override");
        printer.printLine("public boolean has" + name.nameToJavaClassName() + "() throws RepositoryException {");
        printer.incrementIndent();
        printer.printLine("return node.hasProperty(" + constantName + ");");
        printer.decrementIndent();
        printer.printLine("}");
        printer.appendLine();

        // generate get... method
        printer.printLine("@Override");
        printer.printLine("public " + Name.propertyTypeToJava(property.getType()) + (property.isMultiple() ? "[]" : "") + " get" + name.nameToJavaClassName() + "() throws RepositoryException {");
        printer.incrementIndent();
        if (property.isMultiple()) {
            printer.printLine("if (!has" + name.nameToJavaClassName() + "())");
            printer.incrementIndent();
            printer.printLine("return null;");
            printer.decrementIndent();
            printer.appendLine();
            printer.printLine("Value[] values =  node.getProperty(" + constantName + ").getValues();");
            printer.printLine(typeToValueGetter.get(Name.propertyTypeToJava(property.getType())) + "[] results = new " + typeToValueGetter.get(Name.propertyTypeToJava(property.getType())) + "[values.length];");
            printer.printLine("for (int i = 0; i < values.length; i++)");
            printer.incrementIndent();
            printer.printLine("results[i] = values[i].get" + typeToValueGetter.get(Name.propertyTypeToJava(property.getType())) + "();");
            printer.decrementIndent();
            printer.printLine("return results;");
        } else {
            printer.printLine("if (!has" + name.nameToJavaClassName() + "())");
            printer.incrementIndent();
            printer.printLine("return " + typeToNullValue.get(Name.propertyTypeToJava(property.getType())) + ";");
            printer.decrementIndent();
            printer.appendLine();
            printer.printLine("return node.getProperty(" + constantName + ").get" + typeToValueGetter.get(Name.propertyTypeToJava(property.getType())) + "();");
        }
        printer.decrementIndent();
        printer.printLine("}");
        printer.appendLine();

        // generate set... method
        printer.printLine("@Override");
        printer.printLine("public void set" + name.nameToJavaClassName() + "("
                + Name.propertyTypeToJava(property.getType())
                + (property.isMultiple() ? "[]" : "")
                + " " + name.nameToJavaVariableName() + ") throws RepositoryException {");
        printer.incrementIndent();
        if (property.isMultiple())
            if (Name.propertyTypeToJava(property.getType()).equals("String"))
                printer.printLine("node.setProperty(" + constantName + ", " + name.nameToJavaVariableName() + ");");
            else {
                printer.printLine("String[] values = new String[" + name.nameToJavaVariableName() + ".length];");
                printer.printLine("for (int i = 0; i < values.length; i++)");
                printer.incrementIndent();
                printer.printLine("values[i] = \"\" + " + name.nameToJavaVariableName() + "[i];");
                printer.decrementIndent();
                printer.printLine("node.setProperty(" + constantName + ", values);");
            }
        else
            printer.printLine("node.setProperty(" + constantName + ", " + name.nameToJavaVariableName() + ");");
        printer.decrementIndent();
        printer.printLine("}");
        printer.appendLine();
    }

    public void writeChild(ChildDefVO child, String interfaceName) throws IOException {
        Name name;

        if (child.getName() != null)
            name = new Name(child.getName());
        else
            name = new Name(child.getPrimaryType());
        
        String constantName = null;
        if (interfaceName != null)
            constantName = interfaceName + "." + name.nameToConstant();
        else
            constantName = name.nameToConstant();

        Name type = new Name(child.getPrimaryType());

        printer.printLine("@Override");
        printer.printLine("public boolean has" + name.nameToJavaClassName() + "() throws RepositoryException {");
        printer.incrementIndent();
        printer.printLine("return node.hasNode(" + constantName + ");");
        printer.decrementIndent();
        printer.printLine("}");
        printer.appendLine();

        // generate get... method
        printer.printLine("@Override");
        printer.printLine("public " + type.nameToJavaPackageAndClassName() + (child.isMultiple() ? "[]" : "") + " get" + name.nameToJavaClassName() + "() throws RepositoryException {");
        if (child.isMultiple()) {
            printer.incrementIndent();
            printer.printLine("NodeIterator ni = node.getNodes(" + constantName + ");");
            printer.printLine(type.nameToJavaPackageAndClassName() + "[] result = new " + type.nameToJavaPackageAndClassName() + "[(int) ni.getSize()];");
            printer.appendLine();
            printer.printLine("int i = 0;");
            printer.printLine("while (ni.hasNext())");
            printer.incrementIndent();
            printer.printLine("result[i++] = new " + type.nameToJavaPackageAndClassName() + DAO_SUFFIX + "(ni.nextNode());");
            printer.decrementIndent();
            printer.appendLine();
            printer.printLine("return result;");
        } else {
            printer.incrementIndent();
            printer.printLine("return new " + type.nameToJavaPackageAndClassName() + DAO_SUFFIX + "(node.getNode(" + constantName + "));");

        }
        printer.decrementIndent();
        printer.printLine("}");
        printer.appendLine();

        // generate add... or set... method
        printer.printLine("@Override");
        printer.printLine("public void set" + name.nameToJavaClassName() + "(" + type.nameToJavaPackageAndClassName() + " " + name.nameToJavaVariableName() + ") throws RepositoryException {");
        printer.incrementIndent();
        printer.printLine("node.getSession().getWorkspace().copy(" + name.nameToJavaVariableName() + ".getNode().getPath(), node.getPath() + " + constantName + ");");
        printer.decrementIndent();
        printer.printLine("}");
        printer.appendLine();
    }
}
